package equipments;

import java.util.HashMap;

import characters.Characters;

import equipments.enumeration.AccessoriesSlots;
import equipments.enumeration.ArmorSlots;
import equipments.enumeration.EquipmentSlots;
import equipments.enumeration.HandsSlots;
import equipments.enumeration.WeaponsType;

import systems.exceptions.equipments.CanNotEquipThisItem;
import systems.exceptions.equipments.ItemIsNullException;
/**
 * Provides a class to handle a equipment set.
 * @version 1.0
 * @author Baptiste Legrand & Benjamin Orman
 */
/** An Equipment set contains 13 items :
 *  - 1 slot per hand
 *  - 5 armors pieces (Jacket, Gloves, Pauldrons, Leg, Foot)
 *  - 7 accessories :
 *    ->1 Head
 *    ->2 Rings
 *    ->1 Necklace
 *    ->2 Earrings
 *    ->1 Belt
 *  **/
import tools.ItemVisitor;

/**
 * @author Baptiste Legrand and Benjamin Orman
 * @version 2.0
 */
public class Equipment {

  private HashMap<ArmorSlots, Armor>       armorPieces;      /** Contains 5 armors pieces **/
  private HashMap<AccessoriesSlots, Items> accessoriesPieces;/** Contains 7 accessories   **/
  //TODO: Preciser le type des Accessories
  private HashMap<HandsSlots, Items>        hand;            /** Contains 2 slots (one per hand)**/
  //TODO: Preciser le type des objets MainHand. En effet, les non-ambidextes ne peuvent utiliser que des armes dans la handMain mais quand est il des ambidextes ? Peuvent ils utiliser des Shields de cette main ?
  //TODO: Ici on semble oblig� de mettre Items car les shields sont des Armor alors que les weapons (ambidexterie) sont des Weapon
  private ItemVisitor visitor; /**ItemVisitor used to remove bonus value from a character on equipment update **/
  private Characters character; /**a Characters variable used to build the item visitor**/
  /**
   * This constructor creates an empty equipment.
   */
  public Equipment()
  {
    this.armorPieces        = new HashMap<ArmorSlots, Armor>();
    this.accessoriesPieces  = new HashMap<AccessoriesSlots, Items>();
    this.hand               = new HashMap<HandsSlots, Items>();
    this.fillEmpty();
  }

  /**
   * Provides a method which could be used to put all the enumValue
   * as <b>key</b> for each HashMap with a <b>value</b> set to <i>null</i>.
   */
  public void fillEmpty()
  {
    //Armors parts
    for(ArmorSlots enumValue : ArmorSlots.values())
      this.armorPieces.put(enumValue, null);

    //Accessories parts
    for(AccessoriesSlots enumValue : AccessoriesSlots.values())
      this.accessoriesPieces.put(enumValue, null);

    //Hands parts
    for(HandsSlots enumValue : HandsSlots.values())
      this.hand.put(enumValue, null);

  }

  /**
   * Provides a setter to update/replace a part of the armor.
   * When you use this method, you must update the Character's statistics using
   * characters.Character.updateStat.
   * @see equipments.enumeration.ArmorSlots for more details about the
   * possible identifiers.
   * @param type The identifier which describe where to put the armor part.
   * @param newArmorPiece The new armor part.
   */
  public void updateArmor(ArmorSlots type, Armor newArmorPiece)
  {
    if(this.armorPieces.get(type)!=null)
    {
      //System.out.println("wouhou je dois remove les bonus");
      this.armorPieces.get(type).acceptBonusRemoval(visitor);
    }
    this.armorPieces.put(type, newArmorPiece);
  }

  /**
   * Provides a setter to update/replace an accessory.
   * When you use this method, you must update the Character's statistics using
   * characters.Character.updateStat.
   * @see equipments.enumeration.AccessoriesSlots for more details about the 
   * possible identifiers.
   * @param type The identifier which describe where to put the accessory.
   * @param newAccessory The new accessory.
   */
  public void updateAccessory(AccessoriesSlots type, Items newAccessory)
  {
    //System.out.println("updateAccessory : Debut");
    if(this.accessoriesPieces.get(type)!=null)
    {
      this.accessoriesPieces.get(type).acceptBonusRemoval(visitor);
    }
    this.accessoriesPieces.put(type, newAccessory);
    //System.out.println("updateAccessory : Fin");
  }

  /**
   * Provides a setter to update/replace an hand item into one of the two slots
   * available.
   * When you use this method, you must update the Character's statistics using
   * characters.Characters.updateStat.
   * @see equipments.enumeration.HandsSlots for more details about the possible
   * identifiers.
   * @param slot The identifier which describe which hand update with the new
   * item.
   * @param newHandItem The new item to equip.
   * @throws CanNotEquipThisItem This exception will be throws if you try to 
   * execute an unauthorized manipulation with the Item.
   * (E.G: Put a Weapons into the off hand of not ambidextrous Character)
   */
  public void updateHand(HandsSlots slot, Items newHandItem, Characters character)
  throws CanNotEquipThisItem
  {
    //If the item is NULL ...
    if(newHandItem == null)
    {
      //... start to unequip the old one
      if(this.hand.get(slot)!=null)
        this.hand.get(slot).acceptBonusRemoval(visitor);
      this.hand.put(slot, null);
      return;
    }
    switch(slot)
    {
      case MAIN_HAND:
        //If it's a weapon ...
        if(newHandItem instanceof Weapon)
        {
          //... and If it's a 2 hands weapons ...
          if(((Weapon) newHandItem).isTwoHand())
            //...we must unequip the off hand and ...
            this.hand.put(HandsSlots.OFF_HAND, null);
          //... we equip the new item.
          if(this.hand.get(HandsSlots.MAIN_HAND)!=null)
          {
            this.hand.get(HandsSlots.MAIN_HAND).acceptBonusRemoval(visitor);
          }
          this.hand.put(slot, (Weapon)newHandItem);
          return;
        }else{
          throw new CanNotEquipThisItem("Main hand can only be equiped with weapon (even if your character is ambidextrous).");
        }
      case OFF_HAND:
        //If it's a weapon ...
        if(newHandItem instanceof Weapon)
        {
          //... and if the character is ambidextrous ...
          if(character.isAmbidextrous())
          {
            //... and If it's a 2 hands weapon ...
            if(((Weapon) newHandItem).isTwoHand())
              //... we will equip it on the main hand
              this.updateHand(HandsSlots.MAIN_HAND, newHandItem, character);
            //...or if it's not a 2 hands weapon ...
            else
              //... we will equip it
              if(this.hand.get(HandsSlots.OFF_HAND)!=null)
              {
                this.hand.get(HandsSlots.OFF_HAND).acceptBonusRemoval(visitor);
              }
            this.hand.put(slot, newHandItem);
            //... and return;
            return;
          }else //if we try to equip a weapon on the off hand of a not ambidextrous character -> exception
            throw new CanNotEquipThisItem("You can not equip your off hand with a weapon since your are not ambidextrous.");
        }
        //if it's an armor which can be used as shield ...
        if(newHandItem instanceof Armor && ((Armor)newHandItem).isShield())
        {
          //...if the main hand handles a weapon and if it is a bow we will unequip the bow before ...
          if(this.hand.get(HandsSlots.MAIN_HAND) != null
              && ((Weapon)this.hand.get(HandsSlots.MAIN_HAND)).getType() == WeaponsType.bow
          )

            this.hand.put(HandsSlots.MAIN_HAND, null);
          //... we will equip it
          if(this.hand.get(HandsSlots.MAIN_HAND)!=null)
          {
            this.hand.get(HandsSlots.MAIN_HAND).acceptBonusRemoval(visitor);
          }
          this.hand.put(slot, newHandItem); 
        }//... or if it's not a armor which can be used as shield -> except
        else
          throw new CanNotEquipThisItem("Off hand can only be equiped with shield, or with weapon if your character is ambidextrous.");
    }
  }

  /**
   * Getter which returns a specific armor part according to ArmorSlots 
   * parameter.
   * @see equipments.enumaration.ArmorSlots for more details about the 
   * possible identifiers.
   * @param enumType The identifiers of the armor part request.
   * @return The specific armor part.
   * @throws ItemIsNullException If the attached Item is not defined
   */
  public Armor getArmorPart(ArmorSlots enumType)
  throws ItemIsNullException
  {
    if(this.armorPieces.get(enumType) == null)
      throw new ItemIsNullException();
    return this.armorPieces.get(enumType);
  }

  /**
   * Getter which returns a specific accessory part according to 
   * AccessoriesSlots parameter.
   * @see equipments.enumaration.AccessoriesSlots for more details about the
   * possible identifiers.
   * @param enumType - The identifiers of the accessory requested.
   * @return The specific accessory.
   * @throws ItemIsNullException - If the attached Item is not defined
   */
  public Items getAccessory(AccessoriesSlots enumType)
  throws ItemIsNullException
  {
    if(this.accessoriesPieces.get(enumType) == null)
      throw new ItemIsNullException();
    return this.accessoriesPieces.get(enumType);
  }

  /**
   * Getter which returns a specific main hand item according to the slot 
   * number defined by the handEnum parameter.
   * @see equipments.enumeration.HandsSlots for more details about the
   * possible identifiers
   * @return The specific main hand item.
   * @throws ItemIsNullException - If the attached Item is not defined
   */
  public Items getMainHandItem()
  throws ItemIsNullException
  {
    Items handItem = this.hand.get(HandsSlots.MAIN_HAND);
    if(handItem == null)
      throw new ItemIsNullException();
    return handItem;
  }

  /**
   * Getter which returns a specific off hand item according to the slot 
   * number defined by the handEnum parameter.
   * @see equipments.enumeration.HandsSlots for more details about the 
   * possible identifiers
   * @return The specific off hand item.
   * @throws ItemIsNullException - If the attached Item is not defined
   */
  public Items getOffHandItem()
  throws ItemIsNullException
  {
    Items handItem = this.hand.get(HandsSlots.OFF_HAND);
    if(handItem == null)
      throw new ItemIsNullException();
    return handItem;
  }

  /**
   * This getter allows user to get the item linked to an EquipmentSlots
   * given.
   * @see equipments.enumeration.EquipmentSlots for more details about the 
   * possible identifiers
   * @param slot - The EquipmentSlots where to get the item.
   * @return The item linked to the slot. Return null if nothing.
   */
  public Items getItem(EquipmentSlots slot)
  {
    //TODO Visitor Design Pattern ?
    try{
      if(slot instanceof ArmorSlots)
        return getArmorPart((ArmorSlots) slot);
      if(slot instanceof AccessoriesSlots)
        return getAccessory((AccessoriesSlots) slot);
      if(slot instanceof HandsSlots)
        return getHand((HandsSlots)slot);
    }catch(ItemIsNullException nullException)
    {}
    return null;
  }

  /**
   * Getter which returns the item linked to the specifics slot.
   * @see equipments.enumeration.HandsSlots for more details about the 
   * possible identifiers
   * @param slot The HandsSlots value where to get the item
   * @return The item linked to the hand slot.Return (or throw) null if nothing
   * @throws ItemIsNullException - If the attached Item is not defined
   */
  private Items getHand(HandsSlots slot)
  throws ItemIsNullException
  {
    switch(slot)
    {
      case MAIN_HAND : return this.getMainHandItem();
      case OFF_HAND : return this.getOffHandItem();
    }
    return null;
  }

  public void setCharacter(Characters character) {
    this.character = character;
    this.visitor= new ItemVisitor(character);
  }

  public Characters getCharacter() {
    return character;
  }

  public String getID(EquipmentSlots slot) {
    int id=-1;
    try {
      if(slot instanceof ArmorSlots) 
        id = getArmorPart((ArmorSlots) slot).getID();
      if(slot instanceof HandsSlots)
        id = getHand((HandsSlots)slot).getID();
      if(slot instanceof AccessoriesSlots)
        id = getAccessory((AccessoriesSlots)slot).getID();
    } catch (ItemIsNullException e) {
      id = -1;
    }
    return String.valueOf(id);
  }
  
  public void addEquipment(Items item, EquipmentSlots slot) throws CanNotEquipThisItem
  {
    if(slot instanceof ArmorSlots) 
     updateArmor((ArmorSlots) slot, (Armor) item);
    if(slot instanceof HandsSlots)
      updateHand((HandsSlots) slot, item, this.getCharacter());
    if(slot instanceof AccessoriesSlots)
      updateAccessory((AccessoriesSlots) slot, item);
  }
}
